﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Tomahawk.Runtime.Debug.Exceptions;
using Tomahawk.Runtime.Canvas.Sprite;

namespace Tomahawk.Runtime.Canvas
{
    /// <summary>
    /// Provides an optimized interface for 2D Sprite rendering
    /// </summary>
    public class CanvasManager
    {

        /// <summary>
        /// Canvas layers
        /// </summary>
        public enum CanvasLayers
        {
            Background1 = 0,
            Background2,
            Background3,

            Canvas1,
            Canvas2,
            Canvas3,

            Foreground1,
            Foreground2,
            Foreground3,

            ForegroundTopmost,

            MAX_CANVAS_LAYERS
        }

        private GraphicsDevice graphicsDevice;

        /// <summary>
        /// Constructor
        /// </summary>
        public CanvasManager(GraphicsDevice graphicsDevice)
        {
            //stores the graphics device
            this.graphicsDevice = graphicsDevice;

            //create the renderers collection
            this.canvasRenderers = new SortedList<int, ICanvasPrimitiveRenderer>();

            //instance the default sprite groups
            this.createDefaultPrimitiveRenderers();
        }

        #region "Sprite Effect Management"
        private Dictionary<string, SpriteEffect> spriteEffects = new Dictionary<string, SpriteEffect>();

        internal bool RegisterEffectToChannel(SpriteEffect effect, string channel)
        {
            SpriteEffect currentEffect;
            if (!spriteEffects.TryGetValue(channel, out currentEffect))
            {
                spriteEffects.Add(channel, effect);
                return true;
            }
            return false;
        }

        internal bool UnregisterEffectFromChannel(SpriteEffect effect, string channel)
        {
            SpriteEffect currentEffect;
            if (spriteEffects.TryGetValue(channel, out currentEffect))
            {
                if (currentEffect == effect)
                {
                    spriteEffects.Remove(channel);
                    return true;
                }
            }
            return false;
        }

        internal SpriteEffect GetEffectAssignedToChannel(string channel)
        {
            SpriteEffect currentEffect = null;
            spriteEffects.TryGetValue(channel, out currentEffect);
            return currentEffect;
        }
        #endregion

        #region "Virtual canvas"

        public void ApplyVirtualCanvas(ref Microsoft.Xna.Framework.Rectangle canvasRectangle, ref Microsoft.Xna.Framework.Rectangle screenRectangle)
        {
            Microsoft.Xna.Framework.Vector2 virtualCanvasScale = Engine.Instance.VirtualCanvasScale;

            screenRectangle.X = (int)(virtualCanvasScale.X * canvasRectangle.X);
            screenRectangle.Y = (int)(virtualCanvasScale.Y * canvasRectangle.Y);
            screenRectangle.Width = (int)(virtualCanvasScale.X * canvasRectangle.Width);
            screenRectangle.Height = (int)(virtualCanvasScale.Y * canvasRectangle.Height);
        }

        public void ApplyVirtualCanvas(ref Microsoft.Xna.Framework.Vector2 canvasPosition, ref Microsoft.Xna.Framework.Vector2 screenPosition)
        {
            Microsoft.Xna.Framework.Vector2 virtualCanvasScale = Engine.Instance.VirtualCanvasScale;

            screenPosition.X = virtualCanvasScale.X * canvasPosition.X;
            screenPosition.Y = virtualCanvasScale.Y * canvasPosition.Y;
        }

        public void ApplyVirtualCanvas(float canvasSize, out float screenSize)
        {
            Microsoft.Xna.Framework.Vector2 virtualCanvasScale = Engine.Instance.VirtualCanvasScale;

            screenSize = 0.5f * (virtualCanvasScale.X + virtualCanvasScale.Y) * canvasSize;
        }

        #endregion

        #region "SpriteGroups management"

        /// <summary>
        /// List of primitive renderers defined. Sorted by render priority
        /// </summary>
        SortedList<int, ICanvasPrimitiveRenderer> canvasRenderers;

        /// <summary>
        /// Add a spriteGroup to the list
        /// </summary>
        public void RegisterCanvasPrimitiveRenderer(ICanvasPrimitiveRenderer newCanvasRenderer, CanvasLayers layer)
        {
            //add to the array
            canvasRenderers.Add((int)layer, newCanvasRenderer);

            //initialize it
            newCanvasRenderer.Initialize(this.graphicsDevice);
        }

        /// <summary>
        /// Empty the canvas primitives renderers list
        /// </summary>
        public void ClearCanvasPrimitiveGroups()
        {
            foreach (ICanvasPrimitiveRenderer renderer in this.canvasRenderers.Values)
            {
                //TODO: Destroy sprite group
            }
            //empty the list
            this.canvasRenderers.Clear();
        }

        #region "Tomahawk's default primitive renderers"

        private Sprite.UISpriteRenderer[] uiSpriteRenderers;

        /// <summary>
        /// Create defaults sprite groups
        /// </summary>
        private void createDefaultPrimitiveRenderers()
        {
            //array to store the ui renderers
            uiSpriteRenderers = new Sprite.UISpriteRenderer[(int)CanvasLayers.MAX_CANVAS_LAYERS];

            //rendererers for standar GUI textured tiles
            this.createDefaultUISpriteRenderer(CanvasLayers.Background1);
            this.createDefaultUISpriteRenderer(CanvasLayers.Background2);
            this.createDefaultUISpriteRenderer(CanvasLayers.Background3);
            this.createDefaultUISpriteRenderer(CanvasLayers.Canvas1);
            this.createDefaultUISpriteRenderer(CanvasLayers.Canvas2);
            this.createDefaultUISpriteRenderer(CanvasLayers.Canvas3);
            this.createDefaultUISpriteRenderer(CanvasLayers.Foreground1);
            this.createDefaultUISpriteRenderer(CanvasLayers.Foreground2);
            this.createDefaultUISpriteRenderer(CanvasLayers.Foreground3);
            this.createDefaultUISpriteRenderer(CanvasLayers.ForegroundTopmost);
        }

        /// <summary>
        /// Utility method to create a default UISprite renderer for a layer
        /// </summary>
        private void createDefaultUISpriteRenderer(CanvasLayers layer)
        {
            uiSpriteRenderers[(int)layer] = new Sprite.UISpriteRenderer();
            this.RegisterCanvasPrimitiveRenderer(uiSpriteRenderers[(int)layer], layer);
        }

        /// <summary>
        /// Get the UI Sprite Renderer for the given layer
        /// </summary>
        public Sprite.UISpriteRenderer GetUISpriteRenderer(CanvasLayers canvasLayer)
        {
            return this.uiSpriteRenderers[(int)canvasLayer];
        }

        #endregion

        #endregion

        /// <summary>
        /// Render all SpriteGroups in an appropiate order
        /// </summary>
        internal void Render()
        {
            //traverse the SpriteGroup array in priority order and 
            //render their sprites
            foreach (ICanvasPrimitiveRenderer sprGrp in this.canvasRenderers.Values)
            {
                //clear the depth buffer between renders
                //this.graphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

                //render
                sprGrp.Render();
            }

            // Restore the rendering state after using a sprite batch
            Engine.Instance.GraphicsDevice.RenderState.DepthBufferEnable = true;
            Engine.Instance.GraphicsDevice.RenderState.AlphaBlendEnable = false;
            Engine.Instance.GraphicsDevice.RenderState.AlphaTestEnable = false;
        }

        #region "Exceptions"

        /// <summary>
        /// Base exception for all canvas subsystem exceptions
        /// </summary>
        public class CanvasException : Debug.Exceptions.XEngineException
        {
            public CanvasException(ExceptionSeverity severity, String msg)
                : base(severity, msg, null)
            {
            }
        }

        #endregion

    }

}
